﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
using bATR.CacheMonitor;
using bATR.Config;
using bATR.Engines;


namespace bATR.Handlers
{
	
	/// <summary>
	/// The bATR http handler for generating the text image
	/// sends a .png image to the client depending on the supplied request parameters
	/// </summary>
	public class ImageHandlerBase : IHttpHandler
	{

		// The global available bATR configuration variable
		bATRConfig _bATRConfig;
		CacheItemRemovedCallback onRemove = null;

		/// <summary>
		/// Processes the incomming httprequest and sends a PNG image to the context.Response
		/// </summary>
		/// <param name="context">the httpcontext for the request</param>
		public void ProcessRequest(HttpContext context)
		{
			if (HttpRuntime.Cache[bATRConfig.CachedItemsCacheKey] == null)
				this.CachedItems = new Dictionary<string, CacheEntry>();

			System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
			System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

			// read the config
			bATRConfigLogic bcl = new bATRConfigLogic();
			_bATRConfig = bcl.Config;

			// Specify some params that are filled in the parseRequest
			string queryString = context.Request.RawUrl;
			string themeName;
			string styleName;
			string imageText;
			string imageTextBase64;
			bATRStyle fontStyle;
			int maxWidth;
			string fontColor;
			string backgroundColor;


			// Create memorystream for image
			MemoryStream memStream = new MemoryStream();


			// is the object cached allready and is caching enabled?
			if (context.Cache[queryString] != null && _bATRConfig.CachingEnabled == true)
			{
				// Retrieve object from cache
				memStream = ((MemoryStream)context.Cache[queryString]);
			}
			else
			{
				// fill parameters in this parseRequest function
				parseRequest(queryString, out themeName, out styleName, out imageText, out imageTextBase64, out fontStyle, out maxWidth, out fontColor, out backgroundColor);

				if (backgroundColor.Length == 6)
					fontStyle.BackgroundColor = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#" + backgroundColor);
				if (fontColor.Length == 6)
					fontStyle.FontColor = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#" + fontColor);
				// Generates the textimage into the newly created bitmap
				TextImageEngine imageEngine = new TextImageEngine();
				Bitmap imageBitmap = imageEngine.GenerateTextImage(fontStyle, imageText, maxWidth);
				imageBitmap.Save(memStream, ImageFormat.Png);

				// Cache image
				if (_bATRConfig.CachingEnabled && this.CachedItems.Count < _bATRConfig.CachedItemsLimit)
				{
					onRemove = new CacheItemRemovedCallback(this.RemovedCallback);
					HttpRuntime.Cache.Add(queryString, memStream, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, _bATRConfig.CacheExpiration, 0), CacheItemPriority.High, onRemove);
					AddToCache(new CacheEntry(queryString, _bATRConfig.CacheExpiration, memStream.Length));
				}
				else
				{
					RemoveFromCache(queryString);
					context.Cache.Remove(queryString);
				}

				// Dispose
				imageBitmap.Dispose();
			}

			// Set output type and write image to it
			context.Response.Cache.SetCacheability(HttpCacheability.Public);
			context.Response.Cache.SetExpires(DateTime.Now.AddDays(_bATRConfig.ClientCacheExpiration));

			context.Response.ContentType = "image/png";
			memStream.WriteTo(context.Response.OutputStream);
		}


		/// <summary>
		/// Parses a string retrieving usable parameters
		/// </summary>
		/// <param name="stringToParse">the querystring to parse</param>
		/// <param name="themeName">out with the theme</param>
		/// <param name="styleName">out with the style name</param>
		/// <param name="imageText">out with the text to display</param>
		/// <param name="imageTextBase64">out with base64 text</param>
		/// <param name="fontStyle">out with the configured Style</param>
		/// <param name="maxWidth">out with max width for the text</param>
		/// <param name="fontColor">Fontcolor if you want to overrule the style</param>
		/// <param name="backgroundColor">Background color if you want to overrule the style</param>
		private void parseRequest(string stringToParse, out string themeName, out string styleName, out string imageText, out string imageTextBase64, out bATRStyle fontStyle, out int maxWidth, out string fontColor, out string backgroundColor)
		{
			// image text extraction regex
			Regex textRegex = new Regex(_bATRConfig.ImagePath.Replace("~/", "") + "/(?<themeName>.*),(?<styleName>.*),(?<imageText>.*),(?<maxWidth>.*),(?<fontColor>.*),(?<backgroundColor>.*)" + _bATRConfig.ImageExtension, RegexOptions.IgnoreCase | RegexOptions.Singleline);
			
			// Parse the querystring
			Match match = textRegex.Match(stringToParse);

			// Match failed, handle error and stop further processing
			if (!match.Success)
			{
				// Check if the old format is being request (by search engines for example)
				Regex oldFormatTester = new Regex(_bATRConfig.ImagePath.Replace("~/", "") + "/(?<themeName>.*)__(?<styleName>.*)__(?<imageText>.*)__(?<maxWidth>.*)" + _bATRConfig.ImageExtension, RegexOptions.IgnoreCase | RegexOptions.Singleline);
				Match oldMatch = oldFormatTester.Match(stringToParse);

				// Yes, it is the old bATR URL format, throw 404
				if (oldMatch.Success)
				{
					HttpContext.Current.Response.Write("The requested image is called using an old formatted URL");
					HttpContext.Current.Response.StatusCode = 404;
					HttpContext.Current.Response.End();
				}
				// Someone is messing up things... throw exception and stop further processing
				throw new ApplicationException("The requested image is not a valid bATR request.");
			}

			// Retrieve name of the theme	
			themeName = match.Result("${themeName}");
			if (string.IsNullOrEmpty(themeName))
				throw new ApplicationException("themeName undefined");

			// Retrieve name of the style	
			styleName = match.Result("${styleName}");
			if (string.IsNullOrEmpty(styleName))
				throw new ApplicationException("styleName undefined");

			// Get fontstyle
			bATRConfigLogic bcl = new bATRConfigLogic();
			fontStyle = bcl.GetStyle(themeName, styleName);
			
			// Retrieve encoded text
			imageTextBase64 = match.Result("${imageText}");
			// Decode text
			imageText = HttpContext.Current.Server.UrlDecode(System.Text.Encoding.Default.GetString(Convert.FromBase64String(imageTextBase64)));
			

			if (string.IsNullOrEmpty(imageText))
				imageText = "no text defined";
			if (fontStyle.TextToUpper == true)
				imageText = imageText.ToUpper();
			else if (fontStyle.TextToLower == true)
				imageText = imageText.ToLower();

			// Check if there is an max. width
			if (!int.TryParse(match.Result("${maxWidth}"), out maxWidth))
				maxWidth = 0;

			// Check if there is a font and background color
			fontColor = match.Result("${fontColor}");
			backgroundColor = match.Result("${backgroundColor}");
			
			// Check if there is an max. width
			if (!int.TryParse(match.Result("${maxWidth}"), out maxWidth))
				maxWidth = 0;

			
		}


		/// <summary>
		/// Called by .NET when an item is removed from the cache
		/// </summary>
		/// <param name="key"></param>
		/// <param name="value"></param>
		/// <param name="reason"></param>
		public void RemovedCallback(string key, object value, CacheItemRemovedReason reason)
		{
			RemoveFromCache(key);
		}

		/// <summary>
		/// Removes the supplied keyu from the cache
		/// </summary>
		/// <param name="key"></param>
		public void RemoveFromCache(string key)
		{
			Dictionary<string, CacheEntry> items = this.CachedItems;
			items.Remove(key);
			this.CachedItems = items;
		}

		/// <summary>
		/// Adds the supplied item to the cache
		/// </summary>
		/// <param name="entryToCache"></param>
		public void AddToCache(CacheEntry entryToCache)
		{
			Dictionary<string, CacheEntry> items = this.CachedItems;
			items.Add(entryToCache.Key, entryToCache);
			this.CachedItems = items;
		}

		/// <summary>
		/// A collection of the current cached items
		/// </summary>
		public Dictionary<string, CacheEntry> CachedItems
		{
			get
			{
				if (HttpRuntime.Cache[bATRConfig.CachedItemsCacheKey] == null)
					HttpRuntime.Cache[bATRConfig.CachedItemsCacheKey] = new Dictionary<string, CacheEntry>();

				return (Dictionary<string, CacheEntry>)HttpRuntime.Cache[bATRConfig.CachedItemsCacheKey];
			}
			set
			{
				HttpRuntime.Cache[bATRConfig.CachedItemsCacheKey] = value;
			}
		}


		/// <summary>
		/// 
		/// </summary>
		public bool IsReusable
		{
			get { return true; }
		}

	}

}